#pragma once
#include <iostream>
#include <sys/epoll.h>
#include <memory>
#include "Log.hpp"
#include "Socket.hpp"
#include "Epoller.hpp"

static const int num = 64;

class EpollServer : public nocopy
{
public:
    EpollServer(uint16_t port)
        : _port(port)
        , _listensock(new Sock())
        , _epoller(new Epoller())
    {

    }
    void Init()
    {
        _listensock->Socket();
        _listensock->Bind(_port);
        _listensock->Listen();

        log(Info, "sock create success: %d\n", _listensock->Fd());
    }
    void Accepter()
    {
        std::string clientip;
        uint16_t clientport;
        int sock = _listensock->Accept(&clientip, &clientport); //不会再阻塞了
        if(sock > 0)
        {
            //不能直接读取，我们再添加到红黑树里，统一处理
            _epoller->EpollUpdate(EPOLL_CTL_ADD, sock, EPOLLIN);
            log(Info, "get a new link, client info@ %s:%d", clientip.c_str(), clientport);
        }
    }
    void Recver(int fd)
    {
        // 简单模拟事件处理
        char buffer[1024];
        // 从套接字里面读信息
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "get a messge: " << buffer << std::endl;

            //再把数据返回给客户端
            std::string echo_str = "server echo &  ";
            echo_str += buffer;
            write(fd, echo_str.c_str(), echo_str.size());
        }
        else if (n == 0)
        {
            log(Info, "client quit, close fd is : %d", fd);
            //链接一旦断开，直接把红黑树里对应的节点去掉，节省资源
            //移除时，要想保证该文件描述符是合法的，所以要想移除再close关闭
            _epoller->EpollUpdate(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
        else
        {
            log(Warning, "recv error: fd is : %d", fd);
            _epoller->EpollUpdate(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
    }
    void Dispatcher(struct epoll_event revs[], int num)
    {
        for(int i = 0;i < num; i++) //根据num，就可以一次性遍历获取并处理所有已经就绪的事件
        {
            uint32_t events = revs[i].events; //表示已经就绪的事件
            int fd = revs[i].data.fd; //获取已经就绪的文件描述符
            //现在就知道了，哪个文件描述符的哪个事件就绪了
            if (events & EPOLLIN) //读事件就绪了
            {
                if(fd == _listensock->Fd()) //说明来了一个新链接，需要accept获取上来
                {
                    Accepter(); //事件派发
                }   
                else //说明其它fd上面的读事件就绪
                {
                    Recver(fd); //事件处理
                }             
            }
            else if(events & EPOLLOUT) //写事件就绪了
            {

            }
            else //其它事件就绪了
            {

            }
            
            
        }
    }

    void Start()
    {
        //将listensock添加到epoll中，这句话本质是：将listensock和它关心的事件，都给添加到内核epoll模型中的红黑树中
        _epoller->EpollUpdate(EPOLL_CTL_ADD, _listensock->Fd(), EPOLLIN); //我想把“读事件”，“添加”进去
        struct epoll_event revs[num];
        while(true)
        {
            int n = _epoller->EpollerWait(revs, num);
            if(n > 0) //有事件就绪了
            {
                log(Debug, "event happened, fd is : %d", revs[0].data.fd);
                Dispatcher(revs, n); //处理事件逻辑                
            }
            else if(n = 0) //超时了
            {
                log(Info, "time out ...");

            }
            else //错误了
            {
                log(Info, "epoll wait error");

            }
        }
    }
    
    ~EpollServer()
    {

    }
    
private:
    std::shared_ptr<Sock> _listensock;
    std::shared_ptr<Epoller> _epoller;
    uint16_t _port; //端口号
};